# Copyright 2022 CreuBlanca
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from __future__ import annotations

import base64
import io
import json
import logging
import math

from xlsxwriter import Workbook

from odoo import fields, models
from odoo.exceptions import AccessError

_logger = logging.getLogger(__name__)


class SpreadsheetAbstract(models.AbstractModel):
    _name = "spreadsheet.abstract"
    _description = "Spreadsheet abstract for inheritance"

    name = fields.Char(required=True)
    spreadsheet_raw = fields.Serialized()
    spreadsheet_revision_ids = fields.One2many(
        "spreadsheet.oca.revision",
        inverse_name="res_id",
        domain=lambda r: [("model", "=", r._name)],
    )

    def get_spreadsheet_data(self):
        self.ensure_one()
        mode = "normal"
        try:
            self.check_access_rights("write")
            self.check_access_rule("write")
        except AccessError:
            mode = "readonly"
        return {
            "name": self.name,
            "spreadsheet_raw": self.spreadsheet_raw,
            "revisions": [
                {
                    "type": revision.type,
                    "clientId": revision.client_id,
                    "nextRevisionId": revision.next_revision_id,
                    "serverRevisionId": revision.server_revision_id,
                    "commands": json.loads(revision.commands),
                }
                for revision in self.spreadsheet_revision_ids
            ],
            "mode": mode,
        }

    def open_spreadsheet(self):
        self.ensure_one()
        return {
            "type": "ir.actions.client",
            "tag": "action_spreadsheet_oca",
            "params": {"spreadsheet_id": self.id, "model": self._name},
        }

    def send_spreadsheet_message(self, message):
        self.ensure_one()
        channel = (self.env.cr.dbname, "spreadsheet_oca", self._name, self.id)
        message.update({"res_model": self._name, "res_id": self.id})
        if message["type"] in ["REVISION_UNDONE", "REMOTE_REVISION", "REVISION_REDONE"]:
            self.env["spreadsheet.oca.revision"].create(
                {
                    "model": self._name,
                    "res_id": self.id,
                    "type": message["type"],
                    "client_id": message.get("clientId"),
                    "next_revision_id": message["nextRevisionId"],
                    "server_revision_id": message["serverRevisionId"],
                    "commands": json.dumps(message.get("commands", [])),
                }
            )
        self.env["bus.bus"]._sendone(channel, "spreadsheet_oca", message)
        return True

    def write(self, vals):
        if "spreadsheet_raw" in vals:
            self.spreadsheet_revision_ids.unlink()
        return super().write(vals)

    def download(self):
        # config.parse_config(['/etc/odoo/odoo.conf'])  # 指定odoo.conf文件的路径

        # 从配置中获取特定的值
        _logger.info(self.name)

        ss_dic = self.spreadsheet_raw

        output = io.BytesIO()

        styles: dict[str, dict[str, ...]] = ss_dic.get('styles', {})
        formats: dict[str, str] = ss_dic.get('formats', {})
        borders: dict[str, dict[str, list[str]]] = ss_dic.get('borders', [])

        # spreadsheet的样式名称转为xlsxwriter的样式名称
        style_names_mapper = {
            'fontSize': 'font_size', 'textColor': 'font_color',
            'bold': 'bold', 'italic': 'italic', 'underline': 'underline',
            'align': 'align', 'fillColor': 'bg_color',
        }

        # 单元格样式映射
        style_mapper: dict[int, dict] = {}
        for id_, style in styles.items():
            xl_style = {
                xl_name: style[style_name]
                for style_name, xl_name in style_names_mapper.items()
                if style_name in style
            }
            xl_style['valign'] = 'vcenter'
            style_mapper[int(id_)] = xl_style

        # 文本数字格式映射
        num_format_mapper: dict[int, dict] = {
            int(id_): {'num_format': num_format}
            for id_, num_format in formats.items()
        }

        # 单元格边框样式映射
        border_mapper: dict[int, dict] = {}
        for id_, border in borders.items():
            border_style = {}
            for side, lst in border.items():
                border_style[side] = 1
                color = lst[1]
                if len(color) == 4:
                    color = '{0}{1}{1}{2}{2}{3}{3}'.format(*color)
                border_style[f'{side}_color'] = color
            border_mapper[int(id_)] = border_style

        # 格式缓存
        cached_format: dict[tuple[int, int, int], ...] = {}

        sheet_list: list[dict[str, ...]] = ss_dic.get('sheets', [])

        with Workbook(output) as wb:

            for sheet_dic in sheet_list:
                ws = wb.add_worksheet(sheet_dic['name'])

                # 调整列宽
                cols: dict[str, dict] = sheet_dic.get('cols', {})
                for col, dic in cols.items():
                    col = int(col)
                    size = (dic['size'] * 0.11135) if 'size' in dic else None
                    opts = {'hidden': True} if dic.get('isHidden') else None
                    ws.set_column(col, col, size, options=opts)

                # 调整行高
                rows: dict[str, dict] = sheet_dic.get('rows', {})
                for row, dic in rows.items():
                    row = int(row)
                    size = (dic['size'] * 0.75789) if 'size' in dic else None
                    opts = {'hidden': True} if dic.get('isHidden') else None
                    ws.set_row(row, size, options=opts)

                # 冻结窗格
                panes: dict[str, int] = sheet_dic.get('panes')
                if panes:
                    x_split = panes.get('xSplit', 0)
                    y_split = panes.get('ySplit', 0)
                    ws.freeze_panes(y_split, x_split)

                # 写入单元格
                cells: dict[str, dict] = sheet_dic.get('cells', {})

                for cell, dic in cells.items():

                    # 获取单元格格式
                    style_id = dic.get('style', -1)
                    format_id = dic.get('format', -1)
                    border_id = dic.get('border', -1)

                    xl_format_key = style_id, format_id, border_id

                    if xl_format_key not in cached_format:
                        xl_format_opts = (
                                style_mapper.get(style_id, {}) |
                                num_format_mapper.get(format_id, {}) |
                                border_mapper.get(border_id, {})
                        )
                        cached_format[xl_format_key] = wb.add_format(xl_format_opts)

                    fmt = cached_format[xl_format_key]

                    if 'content' in dic:
                        value = dic['content']
                        try:
                            value = float(dic['content'])
                            if math.isnan(value) or math.isinf(value):
                                value = '-'
                        except (Exception,):
                            pass
                        ws.write(cell, value, fmt)
                    else:
                        ws.write_blank(cell, None, fmt)

                # 合并单元格
                merges: list[str] = sheet_dic.get('merges', [])

                for merge_range in merges:
                    first_cell = merge_range.partition(':')[0]

                    if dic := cells.get(first_cell):
                        data = dic.get('content', None)
                        style_id = dic.get('style', -1)
                        format_id = dic.get('format', -1)
                        border_id = dic.get('border', -1)
                        fmt = cached_format[style_id, format_id, border_id]
                    else:
                        data = fmt = None

                    ws.merge_range(merge_range, data, fmt)

                # 筛选
                filter_lst = sheet_dic.get('filterTables', [])
                for each in filter_lst:
                    ws.autofilter(each['range'])

        output.seek(0)

        # Encoding bytes to base64
        encoded_output = base64.b64encode(output.read()).decode('utf-8')

        # Creating attachment record
        attachment = (
            self.env['ir.attachment']
            .create({
                'name': 'last_sale.xlsx',
                'datas': encoded_output,
                'store_fname': 'last_sale.xlsx',
                'res_model': self._name,
                'res_id': 110,
            })
        )
        return {
            'type': 'ir.actions.act_url',
            'url': '/web/content/%s/%s/datas/%s' % (
                attachment._name, attachment.id, attachment.checksum
            ),
            'target': 'new',
        }
